package soar.test.date;

import java.text.SimpleDateFormat;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.ArrayList;
import java.util.Date;

public class App
{
    public static void main(String[] args)
    {
        new App().run();
    }

    private void run()
    {
        try
        {
            String split = "========================================";
            System.out.println(split);

            date();

            System.out.println(split);

            jsr310();
            System.out.println(split);

            limit();
            System.out.println(split);
        }
        catch (Exception e)
        {
            System.err.println(String.format("%s", e));
        }
    }

    private void date() throws Exception
    {
        String split = "------------------------------";

        System.out.println("date");
        System.out.println(split);

        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        format.setLenient(false);

        Date d = new Date();
        String s = format.format(d);
        System.out.println(s);

        d = new Date(0);
        s = format.format(d);
        System.out.println(s);

        s = "1969-12-31 23:59:50";
        d = format.parse(s);
        System.out.println(String.format("date(%s): %d ms", s, d.getTime()));

        d = new SimpleDateFormat("yyyy-MM").parse("2015-10");
        System.out.println(String.format("yyyy-MM 2015-10: %s", d));

        System.out.println(split);

        long ms = System.currentTimeMillis() % (24 * 60 * 60 * 1000);
        System.out.println(String.format("Today %d ms (UTC)", ms));
        Calendar ca = Calendar.getInstance();
        ms = ca.getTimeInMillis() % (24 * 60 * 60 * 1000);
        System.out.println(String.format("Today %d ms (UTC)", ms));
        ms = Clock.systemDefaultZone().millis() % (24 * 60 * 60 * 1000);
        System.out.println(String.format("Today %d ms (UTC)", ms));
        System.out.println(String.format("Today %s",
                new SimpleDateFormat("HH:mm:ss:SSS").format(new Date())));

        System.out.println(split);

        ca = new GregorianCalendar();
        System.out.println("DAY_OF_WEEK: " + ca.get(Calendar.DAY_OF_WEEK));
        System.out.println("DAY_OF_MONTH: " + ca.get(Calendar.DAY_OF_MONTH));

        System.out.println(split);

        SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
        String today = fmt.format(new Date());
        System.out.println(String.format("today: %s", today));

        ca.set(Calendar.DATE, 1);
        String month_from = fmt.format(ca.getTime());
        ca.roll(Calendar.DATE, -1);
        String month_to = fmt.format(ca.getTime());
        System.out
                .println(String.format("month: %s - %s", month_from, month_to));

        ca.set(Calendar.DAY_OF_WEEK, 1);
        String week_from = fmt.format(ca.getTime());
        ca.roll(Calendar.DAY_OF_WEEK, -1);
        String week_to = fmt.format(ca.getTime());
        System.out.println(String.format("week: %s - %s", week_from, week_to));

        System.out.println(split);

        ArrayList<String> last_months = DateUtil.getLastMonths(12);
        for (String month : last_months)
        {
            System.out.println(month);
        }

        System.out.println(split);

        String s1 = "2010-02-28";
        if (!DateUtil.isValidDate(s1))
        {
            System.out.println(String.format("date(%s) invalid", s1));
            return;
        }
        System.out.println(String.format("date(%s) valid", s1));

        int days = -10;
        String s2 = DateUtil.changeDate(s1, days);
        if (s2 == null)
        {
            System.out.println(String.format("date(%s)+%d fail", s1, days));
            return;
        }
        System.out.println(String.format("date(%s)+%d = %s", s1, days, s2));

        String t1 = "2010-02-28 14:10:30";
        if (!DateUtil.isValidTimestamp(t1))
        {
            System.out.println(String.format("timestamp(%s) invalid", t1));
            return;
        }
        System.out.println(String.format("timestamp(%s) valid", t1));

        int seconds = -5 * 60;
        String t2 = DateUtil.changeTimestamp(t1, seconds);
        if (t2 == null)
        {
            System.out.println(
                    String.format("timestamp(%s)+%d fail", t1, seconds));
            return;
        }
        System.out.println(
                String.format("timestamp(%s)+%d = %s", t1, seconds, t2));
    }

    private void jsr310() throws Exception
    {
        String split = "------------------------------";

        System.out.println("jsr310");
        System.out.println(split);

        System.out.println("TimeZone");
        System.out.println(
                String.format("id: %s", TimeZone.getDefault().getID()));
        System.out.println(String.format("rawoffset: %d",
                TimeZone.getDefault().getRawOffset()));
        System.out.println(String.format("display: %s",
                TimeZone.getDefault().getDisplayName()));

        System.out.println();

        System.out.println("ZoneId.systemDefault(): " + ZoneId.systemDefault());
        System.out.println("ZoneOffset(OffsetDateTime): "
                + OffsetDateTime.now().getOffset());
        System.out.println("ZoneOffset(ZonedDateTime): "
                + ZonedDateTime.now().getOffset());
        System.out.println("ZoneOffset(ZoneId): "
                + ZoneId.systemDefault().getRules().getOffset(Instant.now()));
        System.out.println(split);

        System.out.println("Clock");
        System.out.println();

        Clock clock = Clock.systemDefaultZone();
        System.out.println(String.format("clock:          %d", clock.millis()));
        System.out.println("sleep 1s");
        Thread.sleep(1 * 1000);
        System.out.println(String.format("clock:          %d", clock.millis()));
        Clock clock2 = Clock.offset(clock, Duration.ofMinutes(-1));
        System.out
                .println(String.format("clock - 1min:   %d", clock2.millis()));

        System.out.println();

        System.out.println("固定时钟");
        Clock clock_sh = Clock.fixed(Instant.now(), ZoneId.of("Asia/Shanghai"));
        // Clock clock_sh = Clock.fixed(Instant.now(), ZoneId.systemDefault());
        System.out.println(
                String.format("clock Shanghai: %d", clock_sh.millis()));
        System.out.println("sleep 1s");
        Thread.sleep(1 * 1000);
        System.out.println(
                String.format("clock Shanghai: %d", clock_sh.millis()));

        System.out.println(split);

        System.out.println("Instant");
        System.out.println();
        Instant instant = Instant.now();
        System.out.println(String.format("Instant.toEpochMilli: %d ",
                instant.toEpochMilli()));
        System.out.println(String.format("System.currentTimeMillis: %d",
                System.currentTimeMillis()));
        System.out.println("Instant.now(): " + instant);
        System.out.println();

        instant = Clock.systemDefaultZone().instant();
        System.out.println("Clock.systemDefaultZone().instant(): " + instant);
        System.out.println(String.format("秒: %d    纳秒: %d",
                instant.getEpochSecond(), instant.getNano()));
        System.out.println(String.format("毫秒: %d", instant.toEpochMilli()));
        Instant i3 = instant.plus(Duration.ofMinutes(5)).plusSeconds(10);
        Instant instant2 = instant.plus(Duration.ofMinutes(5).plusSeconds(10));
        System.out.println(
                String.format("毫秒 + 5m10s: %d", instant2.toEpochMilli()));
        System.out.println("instant.atZone(ZoneId.systemDefault()): "
                + instant.atZone(ZoneId.systemDefault()));

        System.out.println(split);

        System.out.println("LocalDateTime");
        System.out.println();
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println("LocalDateTime.now(): " + ldt);
        ldt = LocalDateTime.now(ZoneId.systemDefault());
        System.out.println("LocalDateTime.now(ZoneId.systemDefault()): " + ldt);
        ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
        System.out.println(
                "LocalDateTime.now(ZoneId.of(\"Asia/Shanghai\")): " + ldt);
        ldt = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
        System.out.println(
                "LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()): "
                        + ldt);
        System.out.println("Current hour: "
                + LocalTime.now().getLong(ChronoField.HOUR_OF_DAY) + " "
                + LocalTime.now().getHour());
        ZonedDateTime zdt = ZonedDateTime.now();
        System.out.println("ZonedDateTime.now(): " + zdt);
        LocalDateTime ldt2 = ldt.plusMinutes(5).plusSeconds(10);
        System.out.println("+ 5m10s = " + ldt2);
        System.out.println(String.format(
                "LocalDateTime(%s) -> LocalDate(%s) -> -1 month(%s)", ldt,
                ldt.toLocalDate(),
                LocalDateTime.of(ldt.toLocalDate(), LocalTime.of(0, 0))
                        .minus(1, ChronoUnit.MONTHS).format(DateTimeFormatter
                                .ofPattern("yyyy-MM-dd HH:mm:ss"))));
        zdt = ZonedDateTime.ofInstant(
                ldt.atZone(ZoneId.systemDefault()).toInstant(),
                ZoneId.of("Europe/Paris"));
        System.out.println(String
                .format("LocalDateTime(%s) -> ZonedDateTime(%s)", ldt, zdt));

        System.out.println();

        DateTimeFormatter formatter = DateTimeFormatter
                .ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
        System.out.println(String.format("LocalDateTime.now() format: %s",
                formatter.format(LocalDateTime.now())));
        System.out.println(String.format("ZonedDateTime.now() format: %s",
                formatter.format(ZonedDateTime.now())));
        LocalDateTime time = LocalDateTime.of(2020, 8, 30, 14, 45, 11);
        System.out.println(String.format("LocalDateTime format: %s",
                formatter.format(time)));
        time = LocalDateTime.parse("2025-10-02 08:55:33.376", formatter);
        System.out.println(String.format("LocalDateTime parse: %s",
                formatter.format(time)));
        Date date = Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
        System.out.println(String.format("Date from LocalDateTime: %s", date));

        System.out.println(split);

        System.out.println("最近3个月");
        System.out.println();

        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy_MM");
        List<String> months = Stream.iterate(0, n -> n + 1)
                .map(n -> now.minus(n, ChronoUnit.MONTHS).format(format))
                .limit(3).collect(Collectors.toList());
        months.forEach(System.out::println);

        System.out.println(split);

        System.out.println("Date <--> Instant");
        System.out.println();
        instant = Instant.now();
        Thread.sleep(1 * 1000);
        date = new Date();

        System.out.println("Instant: " + instant);
        Date date_from_instant = Date.from(instant);
        System.out.println("Date from Instant: " + date_from_instant);

        System.out.println();

        System.out.println("Date: " + date);
        Instant instant_from_date = date.toInstant(); // Instant.ofEpochMilli(date.getTime());
        System.out.println("Instant from Date: " + instant_from_date);

        System.out.println();

        if (instant.isBefore(instant_from_date))
        {
            System.out.println(
                    String.format("Instant(%s) < Instant from Date(%s)",
                            instant, instant_from_date));
        }

        if (date.after(date_from_instant))
        {
            System.out.println(String.format("Date(%s) > Date from Instant(%s)",
                    date, date_from_instant));
        }

        System.out.println(split);

        System.out.println("Date <--> LocalDateTime");
        System.out.println();
        date = new Date();
        LocalDateTime ldt_from_date = LocalDateTime.ofInstant(date.toInstant(),
                ZoneId.systemDefault());
        // date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        System.out.println(String.format("Date(%s) --> LocalDateTime(%s)", date,
                ldt_from_date));
        Date date_from_ldt = Date
                .from(ldt_from_date.atZone(ZoneId.systemDefault()).toInstant());
        System.out.println(String.format("LocalDateTime(%s) --> Date(%s)",
                ldt_from_date, date_from_ldt));

        System.out.println(split);

        System.out.println("Instant -> 秒");
        instant = Instant.now();
        System.out.println(String.format("Instant(%s) -> second(%d)", instant,
                instant.getEpochSecond()));
        System.out.println();

        System.out.println("秒 -> Instant");
        long seconds = 1371944098;
        System.out.println(String.format("second(%d) -> Instant(%s)", seconds,
                Instant.ofEpochSecond(seconds)));
        System.out.println(String.format("格式化输出(LocalDateTime.ofInstant): %s",
                LocalDateTime
                        .ofInstant(Instant.ofEpochSecond(seconds),
                                ZoneId.systemDefault())
                        .format(DateTimeFormatter
                                .ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))));
        System.out.println(String.format(
                "格式化输出(LocalDateTime.ofEpochSecond): %s",
                LocalDateTime
                        .ofEpochSecond(seconds, 0, ZoneOffset.ofTotalSeconds(
                                TimeZone.getDefault().getRawOffset() / 1000))
                        .format(DateTimeFormatter
                                .ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))));
        System.out.println(String.format(
                "格式化输出(LocalDateTime.ofEpochSecond): %s",
                LocalDateTime
                        .ofEpochSecond(seconds, 0,
                                ZonedDateTime.now().getOffset())
                        .format(DateTimeFormatter
                                .ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))));

        System.out.println();

        System.out.println(split);

        System.out.println("间隔");
        System.out.println();

        LocalDate ld1 = LocalDate.of(2018, 9, 1);
        LocalDate ld2 = LocalDate.of(2017, 8, 10);
        Period period = Period.between(ld1, ld2);
        System.out.println(
                String.format("Period.between(%s, %s): years(%d) days(%d)", ld1,
                        ld2, period.getYears(), period.getDays()));
        System.out.println();

        Instant inst1 = Instant.now();
        Instant inst2 = inst1.minus(999999, ChronoUnit.SECONDS);
        Duration duration = Duration.between(inst1, inst2);
        System.out
                .println(String.format("Duration.between(%s, %s): seconds(%d)",
                        inst1, inst2, duration.getSeconds()));
        System.out.println();

        // SECONDS,HOURS,DAYS --> Instant
        long secs = ChronoUnit.SECONDS.between(inst1, inst2);
        long hours = ChronoUnit.HOURS.between(inst1, inst2);
        long days = ChronoUnit.DAYS.between(inst1, inst2);
        System.out.println(String.format(
                "ChronoUnit between Instant(%s, %s): SECONDS(%d) HOURS(%d) DAYS(%d)",
                inst1, inst2, secs, hours, days));
        // MONTHS,YEARS --> LocalDate
        long mons = ChronoUnit.MONTHS.between(ld1, ld2);
        long years = ChronoUnit.YEARS.between(ld1, ld2);
        System.out.println(String.format(
                "ChronoUnit between LocalDate(%s, %s): MONTHS(%d) YEARS(%d)",
                ld1, ld2, mons, years));
        System.out.println();

        // Instant: SECONDS,HOURS,DAYS
        Instant now_inst = Instant.now();
        Instant before_inst = now_inst.minusSeconds(9999);
        hours = before_inst.until(now_inst, ChronoUnit.HOURS);
        System.out.println(
                String.format("Instant(%s) until Instant(%s): HOURS(%d)",
                        before_inst, now_inst, hours));

        // LocalDateTime: 全部
        LocalDateTime now_ldt = LocalDateTime.now();
        LocalDateTime before_ldt = now_ldt.minusSeconds(999999);
        hours = before_ldt.until(now_ldt, ChronoUnit.HOURS);
        mons = before_ldt.until(now_ldt, ChronoUnit.MONTHS);
        System.out.println(String.format(
                "LocalDateTime(%s) until LocalDateTime(%s): HOURS(%d) MONTHS(%s)",
                before_ldt, now_ldt, hours, mons));
        System.out.println();

        System.out.println(split);

        System.out.println("Cookie expire");
        System.out.println();

        String utc_time = ZonedDateTime.now(ZoneId.of("UTC")).plusSeconds(1000)
                .format(DateTimeFormatter
                        .ofPattern("EEE, dd-MMM-yyyy hh:mm:ss zzz", Locale.US));
        System.out.println(String.format("UTC: %s", utc_time));
        System.out.println();
    }

    private void limit()
    {
        String split = "------------------------------";

        System.out.println("limit");
        System.out.println(split);

        System.out.println("now -> 下一个5分钟时刻");
        Instant now = Instant.now();
        long now_sec = now.getEpochSecond();
        int interval = 5 * 60;
        long next_sec = now_sec - now_sec % interval + interval;
        Instant next = Instant.ofEpochSecond(next_sec);
        System.out.println(String.format("now(%s) --> next(%s)", now, next));
    }
}

class DateUtil
{
    public static ArrayList<String> getLastMonths(int months)
    {
        ArrayList<String> last_months = new ArrayList<>();
        Calendar ca = new GregorianCalendar();
        for (int i = 0; i < months; ++i)
        {
            ca.add(Calendar.MONTH, -1);
            String last_month = new SimpleDateFormat("yyyy-MM")
                    .format(ca.getTime());
            last_months.add(last_month);
        }
        return last_months;
    }

    public static boolean isValidDate(String date)
    {
        try
        {
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            format.setLenient(false);
            format.parse(date);
            return true;
        }
        catch (Exception e)
        {
            System.out.println("parse fail: " + e);
            return false;
        }
    }

    public static boolean isValidTimestamp(String ts)
    {
        try
        {
            SimpleDateFormat format = new SimpleDateFormat(
                    "yyyy-MM-dd HH:mm:ss");
            format.setLenient(false);
            format.parse(ts);
            return true;
        }
        catch (Exception e)
        {
            System.out.println("parse fail: " + e);
            return false;
        }
    }

    public static String changeDate(String orig_date, int days)
    {
        try
        {
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            format.setLenient(false);
            Date od = format.parse(orig_date);
            Calendar cal = new GregorianCalendar();
            cal.setTime(od);
            cal.add(Calendar.DATE, days);
            Date nd = cal.getTime();
            return format.format(nd);
        }
        catch (Exception e)
        {
            System.out.println("parse fail: " + e);
            return null;
        }
    }

    public static String changeTimestamp(String orig_ts, int seconds)
    {
        try
        {
            SimpleDateFormat format = new SimpleDateFormat(
                    "yyyy-MM-dd HH:mm:ss");
            format.setLenient(false);
            Date od = format.parse(orig_ts);
            Calendar cal = new GregorianCalendar();
            cal.setTime(od);
            cal.add(Calendar.SECOND, seconds);
            Date nd = cal.getTime();
            return format.format(nd);
        }
        catch (Exception e)
        {
            System.out.println("parse fail: " + e);
            return null;
        }
    }
}
